/*
 *
 * Copyright (C) 2005-2013 Sourcefire, Inc. All Rights Reserved
 *
 * Written by Sourcefire VRT
 *
 * This file may contain proprietary rules that were created, tested and
 * certified by Sourcefire, Inc. (the "VRT Certified Rules") as well as
 * rules that were created by Sourcefire and other third parties and
 * distributed under the GNU General Public License (the "GPL Rules").  The
 * VRT Certified Rules contained in this file are the property of
 * Sourcefire, Inc. Copyright 2005 Sourcefire, Inc. All Rights Reserved.
 * The GPL Rules created by Sourcefire, Inc. are the property of
 * Sourcefire, Inc. Copyright 2002-2005 Sourcefire, Inc. All Rights
 * Reserved.  All other GPL Rules are owned and copyrighted by their
 * respective owners (please see www.snort.org/contributors for a list of
 * owners and their respective copyrights).  In order to determine what
 * rules are VRT Certified Rules or GPL Rules, please refer to the VRT
 * Certified Rules License Agreement.
 */

/*

# Used rulegen_blacklist-dns.pl with .doesntexist.com, .dnsalias.com, and .dynalias.com to generate
# the base rules used for detection (then cleaned it up a little because it detected a 0-length
# hostname, resulting in a spurious "|00|").  I also added a content match for 1 question and no
# answers, nameservers, or additional records.
alert udp $HOME_NET any -> any 53 (msg:"MISC g01 exploit kit dns request"; flow:to_server; byte_test:1,!&,0xF8,2; content:"|0B|doesntexist|03|com|00|"; fast_pattern:only; content:"|00 01 00 00 00 00 00 00|"; offset:4; depth:8; metadata:service dns, impact_flag red; classtype:trojan-activity; sid:26213;)
alert udp $HOME_NET any -> any 53 (msg:"MISC g01 exploit kit dns request"; flow:to_server; byte_test:1,!&,0xF8,2; content:"|08|dnsalias|03|com|00|"; fast_pattern:only; content:"|00 01 00 00 00 00 00 00|"; offset:4; depth:8; metadata:service dns, impact_flag red; classtype:trojan-activity; sid:26214;)
alert udp $HOME_NET any -> any 53 (msg:"MISC g01 exploit kit dns request"; flow:to_server; byte_test:1,!&,0xF8,2; content:"|08|dynalias|03|com|00|"; fast_pattern:only; content:"|00 01 00 00 00 00 00 00|"; offset:4; depth:8; metadata:service dns, impact_flag red; classtype:trojan-activity; sid:26215;)

Used reference DGA code from https://gist.github.com/jedisct1/5149014

With the three base rules, which have good content matches to enter a minimum number
of times, and the byte_test from the blacklist dns generator to ensure it's the right
type of dns packet, we call into a common detection function that checks to see if
the requested host matches the host that the exploit kit should be requesting.

The DGA is pretty quick, and we should enter a minimum number of times, so
performance should be fine.

*/



#include "sf_snort_plugin_api.h"
#include "sf_snort_packet.h"
#include <string.h>
#include <time.h>

//#define DEBUG
#ifdef DEBUG
#define DEBUG_SO(code) code
#else
#define DEBUG_SO(code)
#endif

// For getting timestamp info from the packets
#ifndef PCAP_PKTHDR
#define PCAP_PKTHDR
typedef u_int bpf_u_int32;

struct pcap_pkthdr {
    struct timeval ts;  /* time stamp */
    bpf_u_int32 caplen; /* length of portion present */
    bpf_u_int32 len;    /* length this packet (off wire) */
};
#endif

/* declare detection functions */
int rule26213eval(void *p);
int rule26214eval(void *p);
int rule26215eval(void *p);

/* declare rule data structures */
// Doing one set of references for all of the rules
static RuleReference rule_g01_DGA_ref1 =
{
    "url", /* type */
    "gist.github.com/jedisct1/5149014" /* value */
};

static RuleReference *rule_g01_DGA_refs[] =
{
   &rule_g01_DGA_ref1,
   NULL
};

/* flow:to_server; */
static FlowFlags rule26213flow0 = 
{
   FLOW_TO_SERVER
};

static RuleOption rule26213option0 =
{
   OPTION_TYPE_FLOWFLAGS,
   {
      &rule26213flow0
   }
};

/* byte_test:size 1, value 0xF8, operator &, offset 2; */
static ByteData rule26213byte_test1 = 
{
   1, /* size */
   CHECK_AND, /* operator */
   0xF8, /* value */
   2, /* offset */
   0, /*multiplier */
   NOT_FLAG|BYTE_BIG_ENDIAN|CONTENT_BUF_NORMALIZED|EXTRACT_AS_BYTE /* flags */
};

static RuleOption rule26213option1 = 
{
   OPTION_TYPE_BYTE_TEST,
   {
      &rule26213byte_test1
   }
};


#ifndef CONTENT_FAST_PATTERN_ONLY
#define CONTENT_FAST_PATTERN_ONLY CONTENT_FAST_PATTERN
#endif
// content:"|0B|doesntexist|03|com|00|", depth 0, fast_pattern:only; 
static ContentInfo rule26213content2 = 
{
   (uint8_t *) "|0B|doesntexist|03|com|00|", /* pattern (now in snort content format) */
   0, /* depth */
   0, /* offset */
   CONTENT_FAST_PATTERN_ONLY|CONTENT_BUF_NORMALIZED, /* flags */
   NULL, /* holder for boyer/moore PTR */
   NULL, /* more holder info - byteform */
   0, /* byteform length */
   0 /* increment length*/
};

static RuleOption rule26213option2 = 
{
   OPTION_TYPE_CONTENT,
   {
      &rule26213content2
   }
};

// content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8; 
static ContentInfo rule26213content3 = 
{
   (uint8_t *) "|00 01 00 00 00 00 00 00|", /* pattern (now in snort content format) */
   8, /* depth */
   4, /* offset */
   CONTENT_BUF_NORMALIZED, /* flags */
   NULL, /* holder for boyer/moore PTR */
   NULL, /* more holder info - byteform */
   0, /* byteform length */
   0 /* increment length*/
};

static RuleOption rule26213option3 = 
{
   OPTION_TYPE_CONTENT,
   {
      &rule26213content3
   }
};

///* references for sid 26213 */
//static RuleReference *rule26213refs[] =
//{
//   NULL
//};

/* metadata for sid 26213 */
/* metadata:service dns; */
static RuleMetaData rule26213service1 = 
{
   "service dns"
};

static RuleMetaData *rule26213metadata[] =
{
   &rule26213service1,
   NULL
};

RuleOption *rule26213options[] =
{
   &rule26213option0,
   &rule26213option1,
   &rule26213option2,
   &rule26213option3,
   NULL
};

Rule rule26213 = {
   /* rule header, akin to => tcp any any -> any any */
   {
      IPPROTO_UDP, /* proto */
      "$HOME_NET", /* SRCIP     */
      "any", /* SRCPORT   */
      0, /* DIRECTION */
      "any", /* DSTIP     */
      "53", /* DSTPORT   */
   },
   /* metadata */
   { 
      3,  /* genid */
      26213, /* sigid */
      3, /* revision */
      "trojan-activity", /* classification */
      0,  /* hardcoded priority */
      "EXPLOIT-KIT g01 exploit kit dns request - doesntexist.com",     /* message */
      rule_g01_DGA_refs, /* ptr to references */
      rule26213metadata /* ptr to metadata */
   },
   rule26213options, /* ptr to rule options */
   &rule26213eval, /* use custom detection function */
   0 /* am I initialized yet? */
};

/* flow:to_server; */
static FlowFlags rule26214flow0 = 
{
   FLOW_TO_SERVER
};

static RuleOption rule26214option0 =
{
   OPTION_TYPE_FLOWFLAGS,
   {
      &rule26214flow0
   }
};

/* byte_test:size 1, value 0xF8, operator &, offset 2; */
static ByteData rule26214byte_test1 = 
{
   1, /* size */
   CHECK_AND, /* operator */
   0xF8, /* value */
   2, /* offset */
   0, /*multiplier */
   NOT_FLAG|BYTE_BIG_ENDIAN|CONTENT_BUF_NORMALIZED|EXTRACT_AS_BYTE /* flags */
};

static RuleOption rule26214option1 = 
{
   OPTION_TYPE_BYTE_TEST,
   {
      &rule26214byte_test1
   }
};


#ifndef CONTENT_FAST_PATTERN_ONLY
#define CONTENT_FAST_PATTERN_ONLY CONTENT_FAST_PATTERN
#endif
// content:"|08|dnsalias|03|com|00|", depth 0, fast_pattern:only; 
static ContentInfo rule26214content2 = 
{
   (uint8_t *) "|08|dnsalias|03|com|00|", /* pattern (now in snort content format) */
   0, /* depth */
   0, /* offset */
   CONTENT_FAST_PATTERN_ONLY|CONTENT_BUF_NORMALIZED, /* flags */
   NULL, /* holder for boyer/moore PTR */
   NULL, /* more holder info - byteform */
   0, /* byteform length */
   0 /* increment length*/
};

static RuleOption rule26214option2 = 
{
   OPTION_TYPE_CONTENT,
   {
      &rule26214content2
   }
};

// content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8; 
static ContentInfo rule26214content3 = 
{
   (uint8_t *) "|00 01 00 00 00 00 00 00|", /* pattern (now in snort content format) */
   8, /* depth */
   4, /* offset */
   CONTENT_BUF_NORMALIZED, /* flags */
   NULL, /* holder for boyer/moore PTR */
   NULL, /* more holder info - byteform */
   0, /* byteform length */
   0 /* increment length*/
};

static RuleOption rule26214option3 = 
{
   OPTION_TYPE_CONTENT,
   {
      &rule26214content3
   }
};

///* references for sid 26214 */
//static RuleReference *rule26214refs[] =
//{
//   NULL
//};

/* metadata for sid 26214 */
/* metadata:service dns; */
static RuleMetaData rule26214service1 = 
{
   "service dns"
};

static RuleMetaData *rule26214metadata[] =
{
   &rule26214service1,
   NULL
};

RuleOption *rule26214options[] =
{
   &rule26214option0,
   &rule26214option1,
   &rule26214option2,
   &rule26214option3,
   NULL
};

Rule rule26214 = {
   /* rule header, akin to => tcp any any -> any any */
   {
      IPPROTO_UDP, /* proto */
      "$HOME_NET", /* SRCIP     */
      "any", /* SRCPORT   */
      0, /* DIRECTION */
      "any", /* DSTIP     */
      "53", /* DSTPORT   */
   },
   /* metadata */
   { 
      3,  /* genid */
      26214, /* sigid */
      3, /* revision */
      "trojan-activity", /* classification */
      0,  /* hardcoded priority */
      "EXPLOIT-KIT g01 exploit kit dns request - dnsalias.com",     /* message */
      rule_g01_DGA_refs, /* ptr to references */
      rule26214metadata /* ptr to metadata */
   },
   rule26214options, /* ptr to rule options */
   &rule26214eval, /* use custom detection function */
   0 /* am I initialized yet? */
};

/* flow:to_server; */
static FlowFlags rule26215flow0 = 
{
   FLOW_TO_SERVER
};

static RuleOption rule26215option0 =
{
   OPTION_TYPE_FLOWFLAGS,
   {
      &rule26215flow0
   }
};

/* byte_test:size 1, value 0xF8, operator &, offset 2; */
static ByteData rule26215byte_test1 = 
{
   1, /* size */
   CHECK_AND, /* operator */
   0xF8, /* value */
   2, /* offset */
   0, /*multiplier */
   NOT_FLAG|BYTE_BIG_ENDIAN|CONTENT_BUF_NORMALIZED|EXTRACT_AS_BYTE /* flags */
};

static RuleOption rule26215option1 = 
{
   OPTION_TYPE_BYTE_TEST,
   {
      &rule26215byte_test1
   }
};


#ifndef CONTENT_FAST_PATTERN_ONLY
#define CONTENT_FAST_PATTERN_ONLY CONTENT_FAST_PATTERN
#endif
// content:"|08|dynalias|03|com|00|", depth 0, fast_pattern:only; 
static ContentInfo rule26215content2 = 
{
   (uint8_t *) "|08|dynalias|03|com|00|", /* pattern (now in snort content format) */
   0, /* depth */
   0, /* offset */
   CONTENT_FAST_PATTERN_ONLY|CONTENT_BUF_NORMALIZED, /* flags */
   NULL, /* holder for boyer/moore PTR */
   NULL, /* more holder info - byteform */
   0, /* byteform length */
   0 /* increment length*/
};

static RuleOption rule26215option2 = 
{
   OPTION_TYPE_CONTENT,
   {
      &rule26215content2
   }
};

// content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8; 
static ContentInfo rule26215content3 = 
{
   (uint8_t *) "|00 01 00 00 00 00 00 00|", /* pattern (now in snort content format) */
   8, /* depth */
   4, /* offset */
   CONTENT_BUF_NORMALIZED, /* flags */
   NULL, /* holder for boyer/moore PTR */
   NULL, /* more holder info - byteform */
   0, /* byteform length */
   0 /* increment length*/
};

static RuleOption rule26215option3 = 
{
   OPTION_TYPE_CONTENT,
   {
      &rule26215content3
   }
};

///* references for sid 26215 */
//static RuleReference *rule26215refs[] =
//{
//   NULL
//};

/* metadata for sid 26215 */
/* metadata:service dns; */
static RuleMetaData rule26215service1 = 
{
   "service dns"
};

static RuleMetaData *rule26215metadata[] =
{
   &rule26215service1,
   NULL
};

RuleOption *rule26215options[] =
{
   &rule26215option0,
   &rule26215option1,
   &rule26215option2,
   &rule26215option3,
   NULL
};

Rule rule26215 = {
   /* rule header, akin to => tcp any any -> any any */
   {
      IPPROTO_UDP, /* proto */
      "$HOME_NET", /* SRCIP     */
      "any", /* SRCPORT   */
      0, /* DIRECTION */
      "any", /* DSTIP     */
      "53", /* DSTPORT   */
   },
   /* metadata */
   { 
      3,  /* genid */
      26215, /* sigid */
      3, /* revision */
      "trojan-activity", /* classification */
      0,  /* hardcoded priority */
      "EXPLOIT-KIT g01 exploit kit dns request - dynalias.com",     /* message */
      rule_g01_DGA_refs, /* ptr to references */
      rule26215metadata /* ptr to metadata */
   },
   rule26215options, /* ptr to rule options */
   &rule26215eval, /* use custom detection function */
   0 /* am I initialized yet? */
};


/* detection functions */

/* check_hostname

   Check if the hostname matches the DGA for the timestamp from the packet.
   Return RULE_MATCH if it matches, and (you guessed it!) RULE_NOMATCH if it doesn't.

   Maximum length of the DGA hostname is 15 characters - portalnaspeedno

   This is important and useful because it means that we can do a simple check once
   to make sure we have enough data without checking at each individual piece.  This
   is because the shorted domain name is 13 characters.  Add 4 bytes for type and
   class, and if we don't have at least 15 characters, our query is truncated and
   total junk and also couldn't possibly have a hostname (our shortest is 4, btw,
   which actually means the minimum amount of data possible after the header is
   13 byte domain name + 4 byte qtype and class + 1 byte size + 4 bytes hostname.

   So, to sum all that up -- A single check for at least 22 bytes after the header
   will both ensure we have the minimum number of bytes necessary to find a matching
   host and ensure we have enough data to check our generated hostname without
   having to check in between reads.
*/
int check_hostname(SFSnortPacket *sp) {

   const uint8_t *cursor_normal, *end_of_payload;

   struct tm *tm;
   uint16_t c0, c1, c2, c3;
   uint16_t d0, d1, d2, d3;

   // New list 26-MAR-2013
   char *dict[] = {
	"t", "speed", "off", "q", "ask", "why", "portal", "un", "m", "is", 
	"po", "le", "us", "order", "host", "na", "p", "own", "call", "as", 
	"j", "o", "old", "no", "si", "h", "ad", "e", "r", "g", 
	"to", "cat", "n", "ko", "how", "i", "tu", "l", "d", "in", 
	"on", "da", "b", "ri", "f", "try", "a", "k", "for", "me", 
	"net", "c", "s"};

   uint8_t dict_len[] = {
	1, 5, 3, 1, 3, 3, 6, 2, 1, 2, 
	2, 2, 2, 5, 4, 2, 1, 3, 4, 2, 
	1, 1, 3, 2, 2, 1, 2, 1, 1, 1, 
	2, 3, 1, 2, 3, 1, 2, 1, 1, 2, 
	2, 2, 1, 2, 1, 3, 1, 1, 3, 2, 
	3, 1, 1};

   uint16_t num_dicts = 53;

   DEBUG_SO(uint8_t namelen; int i;)
   DEBUG_SO(fprintf(stderr, "check_hostname entered\n");)

   if(getBuffer(sp, CONTENT_BUF_NORMALIZED, &cursor_normal, &end_of_payload) <= 0)
      return RULE_NOMATCH;

   // Go to the start of the hostname
   cursor_normal += 13; // txid, options, queries, answers, nameservers, add'l recs, hostname size

   DEBUG_SO(namelen = *(cursor_normal - 1); for(i=0;i<namelen;i++) fprintf(stderr,"%c",cursor_normal[i]); fprintf(stderr,"\n\n");)

   // I skipped over the hostname length above because we are about to prove we have enough
   // data to check our DGA and if our hostname is too short we'll probably fail a memcmp() somewhere

   if(cursor_normal + 22 > end_of_payload) // Not enough data -- see function description
      return RULE_NOMATCH;

   // Now we can just run through the DGA checking each piece and don't need to
   // do further bounds checking.  If we don't match a memcmp(), gtfo.

   // Get the time from the pcap header
   tm = gmtime(&(sp->pcap_header->ts.tv_sec)); // Not thread-safe, but it doesn't matter to us.
   if(tm == NULL)
      return RULE_NOMATCH;

   // Check the hostname piece by piece.

   // The following lines are here to show the original implementation, but what
   // I did was move each piece to after its previous check passed to save cycles
   // in the non-match scenario

   // c0 = hour;
   // c1 = day + c0;
   // c2 = month + c1 - 1;
   // c3 = year + c2;

   // d0 = c0 % num_dicts;
   // d1 = c1 % num_dicts;
   // d2 = c2 % num_dicts;
   // d3 = c3 % num_dicts;

   // if(d1 == d0) d1 = (d1 + 1) % num_dicts;
   // if(d2 == d1) d2 = (d2 + 1) % num_dicts;
   // if(d3 == d2) d3 = (d3 + 1) % num_dicts;
 
   // check the first piece 
   c0 = tm->tm_hour;
   d0 = c0; // no need for % num_dicts because dict_len is longer than 23.

   DEBUG_SO(fprintf(stderr, "hr=%d, d0=%d: %s\n", tm->tm_hour, d0, dict[d0]);)
   if(memcmp(cursor_normal, dict[d0], dict_len[d0]) != 0)
      return RULE_NOMATCH;

   cursor_normal += dict_len[d0];

   // check the second piece
   c1 = tm->tm_mday + c0;
   d1 = c1 % num_dicts;
   if(d1 == d0) d1 = (d1 + 1) % num_dicts;

   DEBUG_SO(fprintf(stderr, "dy=%d, d1=%d: %s\n", tm->tm_mday, d1, dict[d1]);)
   if(memcmp(cursor_normal, dict[d1], dict_len[d1]) != 0)
      return RULE_NOMATCH;

   cursor_normal += dict_len[d1];

   // check the third piece
   c2 = tm->tm_mon + c1; // C gives month base 0 so no need for -1 from ruby src
   d2 = c2 % num_dicts;
   if(d2 == d1) d2 = (d2 + 1) % num_dicts;

   DEBUG_SO(fprintf(stderr, "mn=%d, d2=%d: %s\n", tm->tm_mon, d2, dict[d2]);)
   if(memcmp(cursor_normal, dict[d2], dict_len[d2]) != 0)
      return RULE_NOMATCH;

   cursor_normal += dict_len[d2];

   // check the fourth piece
   c3 = tm->tm_year + 1900 + c2; // tm_year is years since 1900
   d3 = c3 % num_dicts;
   if(d3 == d2) d3 = (d3 + 1) % num_dicts;

   DEBUG_SO(fprintf(stderr, "yr=%d, d3=%d: %s\n", tm->tm_year + 1900, d3, dict[d3]);)
   if(memcmp(cursor_normal, dict[d3], dict_len[d3]) != 0)
      return RULE_NOMATCH;

   // If we get here and we have an FP, the fix for it is to
   // extract the hostname length above and store the pointer
   // at the start of the hostname.  Then we can verify that
   // value == cursor_normal - hostname_ptr + dict_len[d2] + 1.
   // If that *still* FPs, back in the calling function, verify
   // the hostname we found immediately precedes the hostname
   // we matched.  The easiest way to do this is probably to
   // make this function return the length of the hostname (0
   // for nomatch), then use that to locate ourselves for a
   // quick memcmp() in the calling function.
   return RULE_MATCH;   
}

int rule26213eval(void *p) {
   const uint8_t *cursor_normal = 0;
   SFSnortPacket *sp = (SFSnortPacket *) p;

   if(sp == NULL)
      return RULE_NOMATCH;

   if(sp->payload == NULL)
      return RULE_NOMATCH;

   DEBUG_SO(fprintf(stderr, "doesntexist entered\n");)
   
   // flow:to_server;
   if(checkFlow(p, rule26213options[0]->option_u.flowFlags) <= 0)
      return RULE_NOMATCH;
   
   // byte_test:size 1, value 0xF8, operator &, offset 2;
   if(byteTest(p, rule26213options[1]->option_u.byte, cursor_normal) <= 0) 
      return RULE_NOMATCH;
   
   // content:"|0B|doesntexist|03|com|00|", depth 0, fast_pattern:only;
   // if(contentMatch(p, rule26213options[2]->option_u.content, &cursor_normal) <= 0)
   //    return RULE_NOMATCH; 
   
   // content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8;
   if(contentMatch(p, rule26213options[3]->option_u.content, &cursor_normal) <= 0)
      return RULE_NOMATCH;
   
   return check_hostname(sp);
}

int rule26214eval(void *p) {
   const uint8_t *cursor_normal = 0;
   SFSnortPacket *sp = (SFSnortPacket *) p;

   if(sp == NULL)
      return RULE_NOMATCH;

   if(sp->payload == NULL)
      return RULE_NOMATCH;

   DEBUG_SO(fprintf(stderr, "dnsalias entered\n");)
   
   // flow:to_server;
   if(checkFlow(p, rule26214options[0]->option_u.flowFlags) <= 0)
      return RULE_NOMATCH;
   
   // byte_test:size 1, value 0xF8, operator &, offset 2;
   if(byteTest(p, rule26214options[1]->option_u.byte, cursor_normal) <= 0) 
      return RULE_NOMATCH;
   
   // content:"|08|dnsalias|03|com|00|", depth 0, fast_pattern:only;
   // if(contentMatch(p, rule26214options[2]->option_u.content, &cursor_normal) <= 0)
   //    return RULE_NOMATCH; 
   
   // content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8;
   if(contentMatch(p, rule26214options[3]->option_u.content, &cursor_normal) <= 0)
      return RULE_NOMATCH;

   return check_hostname(sp);
}

int rule26215eval(void *p) {
   const uint8_t *cursor_normal = 0;
   SFSnortPacket *sp = (SFSnortPacket *) p;

   if(sp == NULL)
      return RULE_NOMATCH;

   if(sp->payload == NULL)
      return RULE_NOMATCH;
   
   DEBUG_SO(fprintf(stderr, "dynalias entered\n");)

   // flow:to_server;
   if(checkFlow(p, rule26215options[0]->option_u.flowFlags) <= 0)
      return RULE_NOMATCH;
   
   // byte_test:size 1, value 0xF8, operator &, offset 2;
   if(byteTest(p, rule26215options[1]->option_u.byte, cursor_normal) <= 0) 
      return RULE_NOMATCH;
   
   // content:"|08|dynalias|03|com|00|", depth 0, fast_pattern:only;
   // if(contentMatch(p, rule26215options[2]->option_u.content, &cursor_normal) <= 0)
   //    return RULE_NOMATCH; 
   
   // content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8;
   if(contentMatch(p, rule26215options[3]->option_u.content, &cursor_normal) <= 0)
      return RULE_NOMATCH;
   
   return check_hostname(sp);
}

/*
Rule *rules[] = {
    &rule26213,
    &rule26214,
    &rule26215,
    NULL
};
*/
